package swinghacks.ch12.Miscellany.hack93;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;

import javax.swing.AbstractAction;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

public class AWTBlockModels extends JFrame {

	JButton blockButton, dontBlockButton;
	JMenuItem blockMenuItem, dontBlockMenuItem, quitMenuItem;
	JTextField urlField;
	JTextArea contentArea;
	final static String DEFAULT_URL = "http://java.sun.com/j2se/1.4.2/docs/api/java/awt/Component.html";
	Thread loaderThread;
	JProgressBar progressBar;
	javax.swing.Timer progressBarUpdater;

	public AWTBlockModels() {
		super("AWT Thread Blocking");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		initMainLayout();
		initMenus();
		initActions();
	}

	private void initMainLayout() {
		urlField = new JTextField(DEFAULT_URL, 60);
		JPanel topPanel = new JPanel();
		topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));
		topPanel.add(urlField);
		JPanel buttonPanel = new JPanel();
		blockButton = new JButton("Load (blocking)");
		dontBlockButton = new JButton("Load (non-blocking)");
		buttonPanel.add(blockButton);
		buttonPanel.add(dontBlockButton);
		topPanel.add(buttonPanel);
		contentArea = new JTextArea(25, 60);
		JScrollPane scroller = new JScrollPane(contentArea, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
		getContentPane().setLayout(new BorderLayout());
		getContentPane().add(topPanel, BorderLayout.NORTH);
		getContentPane().add(scroller, BorderLayout.CENTER);
		progressBar = new JProgressBar(0, 100);
		getContentPane().add(progressBar, BorderLayout.SOUTH);
	}

	private void initMenus() {
		JMenuBar bar = new JMenuBar();
		JMenu fileMenu = new JMenu("File");
		blockMenuItem = new JMenuItem("Load (blocking)");
		dontBlockMenuItem = new JMenuItem("Load (non-blocking)");
		fileMenu.add(blockMenuItem);
		fileMenu.add(dontBlockMenuItem);
		fileMenu.addSeparator();
		quitMenuItem = new JMenuItem("Quit");
		fileMenu.add(quitMenuItem);
		bar.add(fileMenu);
		setJMenuBar(bar);
	}

	private void initActions() {
		quitMenuItem.addActionListener(new QuitAction());
		BlockingLoadAction blocker = new BlockingLoadAction();
		blockButton.addActionListener(blocker);
		blockMenuItem.addActionListener(blocker);
		NonBlockingLoadAction nonBlocker = new NonBlockingLoadAction();
		dontBlockButton.addActionListener(nonBlocker);
		dontBlockMenuItem.addActionListener(nonBlocker);
	}

	private void makeProgressBarUpdaterFor(NonBlockingURLDocument nbud) {
		final NonBlockingURLDocument updatingDoc = nbud;
		updateProgressBar(0);
		ActionListener callback = new ActionListener() {
			public void actionPerformed(ActionEvent ev) {
				progressBar.setEnabled(true);
				int progress = (int) (updatingDoc.getProgress() * 100);
				updateProgressBar(progress);
				if (!updatingDoc.isAlive())
					progressBarUpdater.stop();
			}
		};
		progressBarUpdater = new javax.swing.Timer(2000, callback);
		progressBarUpdater.start();
	}

	private void updateProgressBar(int progress) {
		// System.out.println ("update progress bar: " + progress);
		if (progress > 0) {
			progressBar.setValue(progress);
		} else
			progressBar.setEnabled(false);
	}

	public static void main(String[] args) {
		AWTBlockModels awtbm = new AWTBlockModels();
		awtbm.pack();
		awtbm.setVisible(true);
	}

	class QuitAction extends AbstractAction {
		public void actionPerformed(ActionEvent e) {
			System.exit(0);
		}
	}

	class BlockingLoadAction extends AbstractAction {
		public void actionPerformed(ActionEvent e) {
			BlockingURLDocument bud = new BlockingURLDocument(urlField.getText());
			progressBar.setEnabled(true);
			progressBar.setValue(0);
			contentArea.setDocument(bud);
			progressBar.setValue(100);
		}
	}

	class NonBlockingLoadAction extends AbstractAction {
		public void actionPerformed(ActionEvent e) {
			NonBlockingURLDocument nbud = new NonBlockingURLDocument(urlField.getText());
			contentArea.setDocument(nbud);
			makeProgressBarUpdaterFor(nbud);
		}
	}

	class BlockingURLDocument extends PlainDocument {
		public BlockingURLDocument(String urlString) {
			super();
			try {
				URL url = new URL(urlField.getText());
				BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
				StringBuffer sbuf = new StringBuffer();
				char[] buffy = new char[16 * 1024];
				int bytesRead = 0;
				while ((bytesRead = in.read(buffy, 0, buffy.length)) > -1) {
					sbuf.append(buffy, 0, bytesRead);
					// if your net connection is too fast to see blocking
					// add the following here
					// Thread.sleep (1000);
				}
				remove(0, getLength());
				insertString(0, sbuf.toString(), null);
			} catch (Exception e) {
				CharArrayWriter writer = new CharArrayWriter();
				e.printStackTrace(new PrintWriter(writer));
				try {
					remove(0, getLength());
					insertString(0, writer.toString(), null);
				} catch (Exception e2) {
					e2.printStackTrace();
				}
			}
		}
	}

	class NonBlockingURLDocument extends PlainDocument implements Runnable {
		protected int length = -1;
		protected int totalBytesRead = 0;
		protected String urlString;
		protected Thread readThread;

		public NonBlockingURLDocument(String urlString) {
			super();
			this.urlString = urlString;
			// start thread here
			readThread = new Thread(this);
			readThread.start();
		}

		public void run() {
			try {
				remove(0, getLength());
				URL url = new URL(urlField.getText());
				URLConnection conn = url.openConnection();
				length = conn.getContentLength();
				System.out.println("length is " + length);
				BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
				// char[] buffy = new char [16 * 1024];
				char[] buffy = new char[80];
				totalBytesRead = 0;
				int bytesRead = -1;
				while ((bytesRead = in.read(buffy, 0, buffy.length)) > -1) {
					final String str = new String(buffy, 0, bytesRead);
					final int finalTBR = totalBytesRead;
					Runnable worker = new Runnable() {
						public void run() {
							try {
								insertString(finalTBR, str, null);
							} catch (BadLocationException ble) {
								ble.printStackTrace();
							}
						}
					};
					SwingUtilities.invokeLater(worker);
					totalBytesRead += bytesRead;
					System.out.println("read " + totalBytesRead + " of " + length + ", progress == " + getProgress());
					// if your net connection is too fast to see updating,
					// make buffy smaller above (maybe 512 bytes) and
					// add the following here:
					Thread.sleep(500);
				}

			} catch (Exception e) {
				CharArrayWriter writer = new CharArrayWriter();
				e.printStackTrace(new PrintWriter(writer));
				try {
					remove(0, getLength());
					insertString(0, writer.toString(), null);
				} catch (Exception e2) {
					e2.printStackTrace();
				}
			} finally {
				readThread = null;
			}
		}

		public boolean isAlive() {
			return (readThread != null) && (readThread.isAlive());
		}

		public float getProgress() {
			return (float) totalBytesRead / length;
		}

	}

}
